\chapter{外部函数}\label{ch23}
\emph{Cyberspace. Unthinkable complexity. Lines of lightranged in the non-space of the mind, clusters andconstellations of data. Like city lights, receding . . .}

\begin{flushright}
    ——William Gibson, \emph{Neuromancer}
\end{flushright}

不幸的是，世界上不是每个程序都是用Rust编写的。我们可能想在我们的Rust程序中使用很多用其他语言实现的优秀的库和接口。Rust的\emph{外部语言接口（foreign function interface(FFI)）}让Rust代码能调用C编写的函数和一部分C++编写的函数。因为大多数操作系统提供C接口，Rust的外部函数接口允许直接访问任何类型的底层设施。

在本章中，我们将编写一个链接到\texttt{libgit2}的程序，它是一个用于Git版本控制系统的C库。首先，我们将展示怎么直接在Rust中使用C函数，就使用我们上一章中介绍的unsafe特性。然后，我们将展示如何构建\texttt{libgit2}的safe接口，借助开源的\texttt{git2-rs} crate的灵感，这个crate正好实现了这一点。

我们将假设你熟悉C语言和编译链接C程序的机制。处理C++也相差不多。我们还假设你熟悉Git版本控制系统。

确实有Rust crate用于和很多其他语言例如Python、JavaScript、Lua和Java交互。我们没有足够的篇幅来介绍它们，但所有这些接口最终都是使用C外部函数接口实现的，因此无论你想和什么语言交互，这一章都能给你一个开头。

\section{寻找公共的数据表示}\label{repr}
Rust和C的公共基础是机器语言，因此为了预测Rust的值在C代码中看起来是什么样的，或者反过来，你需要考虑它们的机器级表示。在整本书中，我们一直着重展示一个值在内存中的实际表示，因此你可能已经注意到了C和Rust的数据世界有很多共通之处：例如Rust的\texttt{usize}和C的\texttt{size\_t}是相同的，两门语言中的结构体也基本相同。为了建立起Rust和C中相应类型的关系，我们将从基本类型开始，并逐渐扩展到更复杂的类型。

鉴于C主要用作系统编程，C中类型的表示总是令人惊讶的宽松：例如一个\texttt{int}通常是32位，但可能会更长，或者短到16位；一个C的\texttt{char}可能是有符号的也可能是无符号的。为了应对这种可变性，Rust的\texttt{std::os::raw}模块定义了一些Rust的类型，这些类型保证和相应的C类型有完全相同的表示（\hyperref[t23-1]{表23-1}）。其中包括基本的整数和字符类型。

\begin{table}[htbp]
    \centering
    \caption{\texttt{std::os::raw}中的Rust类型}
    \label{t23-1}
    \begin{tabular}{ll}
        \textbf{C类型}  &   \textbf{相应的\texttt{std::os::raw}类型}    \\
        \hline
        \texttt{short}          &   \texttt{c\_short}   \\
        \rowcolor{tablecolor}
        \texttt{int}            &   \texttt{c\_int}     \\
        \texttt{long}           &   \texttt{c\_long}    \\
        \rowcolor{tablecolor}
        \texttt{long long}      &   \texttt{c\_longlong}    \\
        \texttt{unsigned short} &   \texttt{c\_ushort}  \\
        \rowcolor{tablecolor}
        \texttt{unsigned, unsigned int} &   \texttt{c\_uint}    \\
        \texttt{unsigned long}  &   \texttt{c\_ulong}   \\
        \rowcolor{tablecolor}
        \texttt{unsigned long long}     &   \texttt{c\_ulonglong}   \\
        \texttt{char}           &   \texttt{c\_char}    \\
        \rowcolor{tablecolor}
        \texttt{signed char}    &   \texttt{c\_schar}   \\
        \texttt{unsigned char}  &   \texttt{c\_uchar}   \\
        \rowcolor{tablecolor}
        \texttt{float}           &   \texttt{c\_float}    \\
        \texttt{double}         &   \texttt{c\_double}  \\
        \rowcolor{tablecolor}
        \texttt{void *, const void *}   &   \texttt{*mut c\_void, *const c\_void}   \\
    \end{tabular}
\end{table}

有关\hyperref[t23-1]{表23-1}的注意事项：
\begin{enumerate}
    \item 除了\texttt{c\_void}之外，这里所有的Rust类型都是某些基本Rust类型的别名：例如\texttt{c\_char}是\\
    \texttt{i8}或者\texttt{u8}。
    \item Rust的\texttt{bool}等价于C或C++的\texttt{bool}。
    \item Rust的32位\texttt{char}类型并不等同于\texttt{wchar\_t}，后者的宽度和编码取决于具体实现。C的\texttt{char32\_t}倒是更接近一点，但它的编码仍然并不保证是Unicode。
    \item Rust的基础\texttt{usize}和\texttt{isize}类型和C的\texttt{size\_t}和\texttt{ptrdiff\_t}有相同的表示。
    \item C/C++指针和C++的引用对应Rust的原始指针类型\texttt{*mut T}和\texttt{*const T}。
    \item 从技术上讲，C标准允许实现使用一些Rust中没有相应类型的表示：36位整数、用符号+数字来表示有符号值等等。在实践中，在Rust被移植到的每个平台上，每个基本的C整数类型在Rust中都有对应的类型。
\end{enumerate}

为了定义兼容C结构体的Rust结构体类型，你可以使用\texttt{\#[repr(C)]}属性。在结构体的定义上面放上\texttt{\#[repr(C)]}可以要求Rust按照C编译器的方式放置结构体中的字段。例如，\texttt{libgit2}的\emph{git2/errors.h}头文件定义了下面的C结构体来提供一个错误的详情：
\begin{minted}{C}
    typedef struct {
        char *message;
        int klass;
    } git_error;
\end{minted}

你可以按照下面这样定义一个内存表示完全相同的Rust类型：
\begin{minted}{Rust}
    use std::os::raw::{c_char, c_int};

    #[repr(C)]
    pub struct git_error {
        pub message: *const c_char,
        pub klass: c_int
    }
\end{minted}

\texttt{\#[repr(C)]}属性只影响struct自身的布局，不会影响单个字段的表示，因此为了和C struct匹配，每一个字段也都要使用C风格的类型：例如用\texttt{*const c\_char}替换\texttt{char *}，用\texttt{c\_int}替换\texttt{int}。

在这个特定的例子中，\texttt{\#[repr(C)]}属性可能并不会改变\texttt{git\_error}的布局。因为实际上没有那么多放置一个指针和一个整数的方法。但C和C++都保证一个结构体的成员按照声明的顺序依次在内存中排布，而Rust会按照总结构体大小最小的方式来组织字段，并且0大小的类型不占用空间。\texttt{\#[repr(C)]}属性告诉Rust按照C的规则来布局。

你也可以使用\texttt{\#[repr(C)]}来控制C风格的enum的表示：
\begin{minted}{Rust}
    #[repr(C)]
    #[allow(non_camel_case_types)]
    enum git_error_code {
        GIT_OK          = 0,
        GIT_ERROR       = -1,
        GIT_ENOTFOUND   = -3,
        GIT_EEXISTS     = -4,
        ...
    }
\end{minted}

通常情况下，Rust在选择如何表示enum时会使用各种技巧。例如，我们提到过Rust在一个单字中存储\texttt{Option<\&T>}（如果\texttt{T}是sized）。如果没有\texttt{\#[repr(C)]}，Rust会使用单个字节来表示\texttt{git\_error\_code} enum；有了\texttt{\#[repr(C)]}之后，Rust会和C一样用一个C \texttt{int}一样大的值来存储。

你可以要求Rust使用和某些整数相同的表示来存储enum。上面的定义如果以\texttt{\#[repr(i16)]}\\
开头，将会得到一个和下面C++ enum相同的16位的表示：
\begin{minted}{C++}
    #include <stdint.h>

    enum git_error_code: int16_t {
        GIT_OK          = 0,
        GIT_ERROR       = -1,
        GIT_ENOTFOUND   = -3,
        GIT_EEXISTS     = -4,
        ...
    };
\end{minted}

正如之前提到的，\texttt{\#[repr(C)]}还可以用于union。\texttt{\#[repr(C)]} union的字段总是从union的内存的第一个位（0偏移处）开始。

假设你有一个C struct使用一个union来存储一些数据，再用一个tag值来指示应该使用union的哪个值，类似于Rust的enum一样：
\begin{minted}{C}
    enum tag {
        FLOAT = 0,
        INT   = 1,
    };

    union number {
        float f;
        short i;
    };

    struct tagged_number {
        tag t;
        number n;
    };
\end{minted}

Rust可以通过对enum、struct和union类型都应用\texttt{\#[repr(C)]}来实现一个这样的结构体，然后使用\texttt{match}语句基于tag来选择一个struct中的union的字段：
\begin{minted}{Rust}
    #[repr(C)]
    enum Tag {
        Float = 0,m
        Int   = 1
    }

    #[repr(C)]
    union FloatOrInt {
        f: f32,
        i: i32,
    }

    #[repr(C)]
    struct Value {
        tag: Tag,
        union: FloatOrInt
    }

    fn is_zero(v: Value) -> bool {
        use self::Tag::*;
        unsafe {
            match v {
                Value { tag: Int, union: FloatOrInt { i: 0 } } => true,
                Value { tag: Float, union: FloatOrInt { f: num } } => (num == 0.0),
                _ => false
            }
        }
    }
\end{minted}

使用这种技术，即使是复杂的结构体也可以很容易地跨FFI边界使用。

在Rust和C之间传递字符串要稍微更难一点。C使用一个空结尾的字符数组的指针来表示字符串。另一边Rust显式地存储字符串的长度，要么是\texttt{String}的一个字段，要么是一个胖引用\texttt{\&str}的第二个字。Rust的字符串不是空字符结尾的；事实上，它们的内容里可能包含空字符，这些空字符和其他字符一样，没有区别。

这意味着你不能借用一个Rust字符串来当做C字符串：如果你传给C代码一个Rust字符串的指针，它可能错误地把内容中一个空字符当做字符串的结尾，或者越界去查找一个不存在的结尾空字符。从另一个方向考虑，你也许可以借用一个C字符串作为一个Rust的\texttt{\&str}，只要它的内容是有效的UTF-8。

这种情况强迫Rust必须把C的字符串看做和\texttt{String}、\texttt{\&str}完全不同的类型。在\texttt{std::ffi}模块中，\texttt{CString}和\texttt{CStr}类型表示拥有所有权的和借用的空字符结尾的字节数组。和\texttt{String}、\texttt{str}比起来，\texttt{CString}和\texttt{CStr}的方法非常有限，基本只有构建自身和转换成其他类型的方法。我们将在下一节中用实例展示这些类型。

\section{声明外部函数和变量}
用\texttt{extern}块来声明其他库中定义的函数和变量，最终的Rust编译出的可执行文件会链接到这些库。例如，在大多数平台上，每个Rust程序都会链接到标准的C库，因此我们可以像这样告诉Rust C库里的\texttt{strlen}函数：
\begin{minted}{Rust}
    use std::os::raw::c_char;

    extern {
        fn strlen(s: *const c_char) -> usize;
    }
\end{minted}

这样就告诉了Rust这个函数的名字和类型，定义将会在之后进行链接。

Rust假设在\texttt{extern}块中声明的函数使用C语言的惯例来传递参数和接受返回值。它们被定义为\texttt{unsafe}函数。对\texttt{strlen}来说，这是正确的选择：它实际上是一个C函数，它的C规范要求你传入一个有效的以空字符结尾的字符串指针，而这是一个Rust无法强制的合约。（几乎任何接受原始指针作为参数的函数都必须是\texttt{unsafe}的：safe Rust可以从任何整数构造原始指针，但解引用这样的指针可能会导致未定义行为。）

有了这个\texttt{extern}块，我们可以像任何其他Rust函数一样调用\texttt{strlen}，尽管它的类型显示了它是别的语言中定义的函数：
\begin{minted}{Rust}
    use std::ffi::CString;

    let rust_str = "I'll be back";
    let null_terminated = CString::new(rust_str).unwrap();
    unsafe {
        assert_eq!(strlen(null_terminated.as_ptr()), 12);
    }
\end{minted}

\texttt{CString::new}函数构建出一个空字符结尾的C字符串。它首先检查它的内容里是否有空字符，因为如果有空字符就不能用C字符串来表示它，如果找到了空字符就返回一个error（因此需要调用\texttt{unwrap}）。否则，它向结尾添加一个空字符并返回一个\texttt{CString}，这个\texttt{CString}持有最后的字符的所有权。

\texttt{CString::new}的开销取决于参数的类型。它接受任何实现了\texttt{Into<Vec<u8>>}的类型，传递一个\texttt{\&str}需要一次内存分配和拷贝，因为它到\texttt{Vec<u8>}的转换需要在堆上构建一份字符串的拷贝，这样vector才能拥有字符串的所有权。但以值传递一个\texttt{String}简单地消耗掉这个字符串并获取它的缓冲区的所有权，因此除非向缓冲区中添加一个空字符需要扩大缓冲区，否则这个转换完全不需要拷贝文本或内存分配。

\texttt{CString}解引用到\texttt{CStr}，后者的\texttt{as\_ptr}方法返回一个\texttt{*const c\_char}指向字符串的头部。这正是\texttt{strlen}期望的类型。在这个例子中，\texttt{strlen}遍历字符串，寻找\texttt{CString::new}添加的空字符，然后返回字符串的长度，即字节数。

你也可以在\texttt{extern}块中声明全局变量，POSIX系统有一个叫做\texttt{environ}的全局变量存储进程的环境变量。在C中，它被声明为：
\begin{minted}{Rust}
    extern char **environ;
\end{minted}

在Rust中，你可以写：
\begin{minted}{Rust}
    use std::ffi::CStr;
    ues std::os::raw::c_char;

    extern {
        static environ: *mut *mut c_char;
    }
\end{minted}

为了打印出环境变量的第一个元素，你可以写：
\begin{minted}{Rust}
    unsafe {
        if !environ.is_null() && !(*environ).is_null() {
            let var = CStr::from_ptr(*environ);
            println!("first environment variable: {}",
                     var.to_string_lossy())
        }
    }
\end{minted}

在确保了\texttt{environ}有第一个元素之后，代码调用了\texttt{CStr::from\_ptr}来构建一个借用它的\texttt{CStr}。\texttt{to\_string\_lossy}方法返回一个\texttt{Cow<str>}：如果C字符串包含有效的UTF-8，\texttt{Cow}会以\texttt{\&str}的形式借用它的内容，不包括结尾的空字符。否则，\texttt{to\_string\_lossy}在堆上构造一份文本的拷贝，把其中非UTF-8的字符序列替换为官方的Unicode替换字符�，并构造出一个拥有它所有权的\texttt{Cow}。无论是哪种情况，结果都实现了\texttt{Display}，因此你可以使用\texttt{\{\}}格式化参数打印出它。

\section{使用库里的函数}
为了使用一个特定的库提供的函数，你可以在\texttt{extern}块上方加上\texttt{\#[link]}属性来指定Rust应该链接的库的名称。例如，这里有一个程序调用\texttt{libgit2}的初始化和结束函数，但不做任何其他事：
\begin{minted}{Rust}
    use std::os::raw::c_int;

    #[link(name = "git2")]
    extern {
        pub fn git_libgit2_init() -> c_int;
        pub fn git_libgit2_shutdown() -> c_int;
    }

    fn main() {
        unsafe {
            git_libgit2_init();
            git_libgit2_shutdown();
        }
    }
\end{minted}

\texttt{extern}和之前一样声明了外部函数。\texttt{\#[link(name = "git2")]}属性会要求当Rust创建最终的可执行文件或者共享库时，它应该链接到\texttt{git2}库。Rust使用系统的链接器来构建可执行文件：在Unix上，它会向链接器的命令行传递\texttt{-lgit2}参数；在Windows上，它会传递\texttt{git2.LIB}参数。

\texttt{\#[link]}属性在库crate中也可以工作。当你构建一个依赖其他crate的程序时，Cargo会从整个依赖图中抓取所有链接项并在最后的链接中全部加进去。

如果你想要在你自己的机器上继续这个例子，你需要自己构建\texttt{libgit2}。我们使用\href{https://libgit2.org/}{libgit2} 0.25.1版本。为了编译\texttt{libgit2}，你需要安装CMake构建工具和Python；我们使用了\href{https://cmake.org/}{CMake} 3.8.0版本和\href{https://www.python.org/}{Python} 2.7.13版本。

构建\texttt{libgit2}的完整文档可以在网站上找到，但因为非常简单所以我们将在这里展示基本的一些步骤。在Linux上，假设你已经把库的源码解压到了目录\emph{/home/jimb/libgit2-0.25.1}：
\begin{minted}{bash}
    $ cd /home/jimb/libgit2-0.25.1
    $ mkdir build
    $ cd build
    $ cmake ..
    $ cmake --build .
\end{minted}

在Linux上，这会生成一个共享库\emph{/home/jimb/libgit2-0.25.1/build/libgit2.so.0.25.1}，还有一些指向它的符号链接，包括有一个叫\emph{libgit2.so}的。在macOS上，结果与此类似，但库的名字叫\emph{libgit2.dylib}。

在Windows上也非常简单。假设你把源码解压到了目录\emph{C:\textbackslash{}Users\textbackslash{}JimB\textbackslash{}libgit2-0.25.1}。在一个Visual Studio的命令提示符中：
\begin{minted}{PowerShell}
    > cd C:\Users\JimB\libgit2-0.25.1
    > mkdir build
    > cd build
    > cmake -A x64 ..
    > cmake --build .
\end{minted}

这些命令和在Linux上用的命令几乎一样，除了在第一次运行CMake的时候必须指定64位的构建来匹配你的Rust编译器。（如果你安装了32位的Rust工具链，那么你应该省略第一条\texttt{cmake}命令的\texttt{-A x64}标记。）这会产生一个导入库\emph{git2.LIB}和一个动态链接库\emph{git2.DLL}，都在目录\emph{C:\textbackslash{}Users\textbackslash{}JimB\textbackslash{}libgit2-0.25.1\textbackslash{}build\textbackslash{}Debug}。（之后的命令都是以Unix为例，如果Windows上的命令大不相同的话会再加上Windows上的命令。）

在一个单独的目录里创建Rust程序：
\begin{minted}{bash}
    $ cd /home/jimb
    $ cargo new --bin git-toy
         Created binary (application) `git-toy` package
\end{minted}

复制我们之前展示的代码并粘贴到\emph{src/main.rs}里。当然，如果你尝试构建，Rust会不知道在哪查找你构建的\texttt{libgit2}库：
\begin{minted}{text}
    $ cd git-toy
    $ cargo run
       Compiling git-toy v0.1.0 (/home/jimb/git-toy)
    error: linking with `cc` failed: exit code: 1
       |
       = note: /usr/bin/ld: error: cannot find -lgit2
               src/main.rs:11: error: undefined reference to 'git_libgit2_init'
               src/main.rs:12: error: undefined reference to 'git_libgit2_shutdown'
               collect2: error: ld returned 1 exit status
    
    error: aborting due to previous error
    error: could not complie `git-toy`
    To learn more, run the command again with --verbose.
\end{minted}

你可以通过编写一个\emph{构建脚本}来告诉Rust在哪搜索这个库，构建脚本是Cargo会在编译期编译并运行的Rust代码。构建脚本可以做很多事：动态生成代码，编译要被包含在crate中的C代码等等。在这个例子中，你需要做的只是向可执行文件的链接命令中添加一个库的搜索路径。当Cargo运行构建脚本时，它会解析构建脚本的输出来获取这类信息，因此构建脚本只需要把正确的信息打印到标准输出就可以了。

为了创建你自己的构建脚本，在\emph{Cargo.toml}所在的目录下添加一个叫\emph{build.rs}的文件，内容如下：
\begin{minted}{Rust}
    fn main() {
        println!(r"cargo:rustc-link-search=native=/home/jimb/libgit2-0.25.1/build");
    }
\end{minted}

这是Linux上的正确路径；在Windows上，你应该把\texttt{native=}之后的路径替换为\\
\emph{C:\textbackslash{}Users\textbackslash{}JimB\textbackslash{}libgit2-0.25.1\textbackslash{}build\textbackslash{}Debug}。（我们忽略了一些细节来保证这个例子的简洁；在一个真实的应用中，你应该避免在构建脚本中使用绝对路径。我们在本节结尾给出了如何正确实现这一点的文档。）

现在你基本上可以运行这个程序了。在macOS上它可能可以立即工作；但在Linux系统上你可能会看到类似这样的输出：
\begin{minted}{text}
    $ cargo run
       Compiling git-toy v0.1.0 (/tmp/rustbook-transcript-tests/git-toy)
        Finished dev [unoptimized + debuginfo] target(s)
         Running `target/debug/git-toy`
    target/debug/git-toy: error while loading shared libraries: libgit2.so.25: cannot open shared object file:
    no such file or directory
\end{minted}

意思是说，尽管Cargo成功地把可执行文件链接到了库，但它不知道怎么在运行时找到共享库。在Windows上通过弹出一个对话框来报告这个错误。在Linux上，你必须设置\texttt{LD\_LIBRARY\_PATH}环境变量：
\begin{minted}{bash}
    $ export LD_LIBRARY_PATH=/home/jimb/libgit2-0.25.1/build:$LD_LIBRARY_PATH
    $ cargo run
        Finished dev [unoptimized + debuginfo] target(s) in 0.0 secs
         Running `target/debug/git-toy`
\end{minted}

在macOS上，你可能需要设置\texttt{DYLD\_LIBRARY\_PATH}。

在Windows上，你必须设置\texttt{PATH}环境变量：
\begin{minted}{PowerShell}
    > set PATH=C:\Users\JimB\libgit2-0.25.1\build\Debug;%PATH%
    > cargo run
        Finished dev [unoptimized + debuginfo] target(s) in 0.0 secs
         Running `target/debug/git-toy`
    >
\end{minted}

当然，在一个要部署的应用中你可能想避免只为了寻找你的库而修改环境变量。一种替代方案是把C库静态链接到你的crate里。这会把库的目标文件拷贝到crate的\emph{.rlib}文件里，和这个crate中的Rust代码生成的目标文件和元数据放在一起。然后这整个集合会参与最终的链接。

Cargo的一个惯例是提供C库访问的crate应该命名为\texttt{LIB-sys}，其中\texttt{LIB}是C库的名称。一个\texttt{-sys} crate应该只包含静态链接的库和包含\texttt{extern}块和类型定义的Rust模块。更高层的接口应该应该在依赖\texttt{-sys} crate的crate中实现。这允许多个上游的crate依赖同一个\texttt{-sys} crate，假设有一个版本的\texttt{-sys} crate可以满足每个上游的需要的话。

有关Cargo的构建脚本和与系统库的链接的完整文档见\href{https://doc.rust-lang.org/cargo/reference/build-scripts.html}{在线Cargo文档}。它展示了如何在构建脚本中避免绝对路径、控制编译选项、使用例如\texttt{pkg-config}的工具，等等。\texttt{git2-rs} crate也提供了模仿的好例子，它的构建脚本处理了一些很复杂的情况。

\section{一个libgit2的原始接口}
如何正确地使用\texttt{libgit2}可以分解为两个问题：
\begin{enumerate}
    \item 如何在Rust中使用\texttt{libgit2}的函数？
    \item 如何通过它们构建一个safe Rust的接口？
\end{enumerate}

我们将逐一解决这两个问题。在本节中，我们将编写一个程序，它基本上是一个巨大的\texttt{unsafe}块，充满了不规范的Rust代码，它反映了类型系统和惯例的冲突，这也是混合语言编程所固有的特点。我们称其为\emph{原始(raw)}接口。它的代码可能有些凌乱，但它能展示出Rust代码使用\texttt{libgit2}时必须的所有步骤。

然后，在下一节中，我们将构建一个\texttt{libgit2}的safe接口，它使用Rust的类型来强迫\emph{libgit2}对使用者隐含的要求。幸运的是，\texttt{libgit2}是一个设计得非常好的C库，因此Rust的安全性要求的问题都有很好的答案，并且我们可以构建出没有\texttt{unsafe}函数的规范的Rust接口。

我们要写的程序非常简单：它以命令行参数的形式接受一个路径，打开那里的Git仓库，然后打印出head commit的信息。但这足以用来展示构建safe和规范的Rust接口的关键策略。

对于原始接口，程序需要很多\texttt{libgit2}中的函数和类型，因此把\texttt{extern}块移动到它自己的模块是有意义的。我们将在\emph{git-toy/src}中创建一个叫\emph{raw.rs}的文件，内容如下：
\begin{minted}{Rust}
    #![allow(non_camel_case_types)]

    use std::os::raw::{c_int, c_char, c_uchar};

    #[link(name = "git2")]
    extern {
        pub fn git_libgit2_init() -> c_int;
        pub fn git_libgit2_shutdown() -> c_int;
        pub fn giterr_last() -> *const git_error;

        pub fn git_repository_open(out: *mut *mut git_repository,
                                   path: *const c_char) -> c_int;
        
        pub fn git_repository_free(repo: *mut git_repository);
        
        pub fn git_reference_name_to_id(out: *mut git_oid,
                                        repo: *mut git_repository,
                                        reference: *const c_char) -> c_int;
        
        pub fn git_commit_lookup(out: *mut *mut git_commit,
                                 repo: *mut git_repository,
                                 id: *const git_oid) -> c_int;

        pub fn git_commit_author(commit: *const git_commit) -> *const git_signature;
        pub fn git_commit_message(commit: *const git_commit) -> *cosnt c_char;
        pub fn git_commit_free(commit: *mut git_commit);
    }

    #[repr(C)] pub struct git_repository { _private: [u8; 0] }
    #[repr(C)] pub struct git_commit { _private: [u8; 0] }

    #[repr(C)]
    pub struct git_error {
        pub message: *const c_char,
        pub klass: c_int
    }

    pub const GIT_OID_RAWSZ: usize = 20;

    #[repr(C)]
    pub struct git_oid {
        pub id: [c_uchar; GIT_OID_RAWSZ]
    }

    pub type git_time_t = i64;

    #[repr(C)]
    pub struct git_time {
        pub time: git_time_t,
        pub offset: c_int
    }

    #[repr(C)]
    pub struct git_signature {
        pub name: *const c_char,
        pub email: *const c_char,
        pub when: git_time
    }
\end{minted}

其中每一项都是根据\texttt{libgit2}的头文件来声明的。例如\\
\emph{libgit2-0.25.1/include/git2/repository.h}中包含这个声明：
\begin{minted}{C}
    extern int git_repository_open(git_repository **out, const char *path);
\end{minted}

这个函数尝试打开\texttt{path}处的Git仓库。如果一切顺利，它会创建一个\texttt{git\_repository}对象并把该对象的指针存储在\texttt{out}指向的位置。等价的Rust声明如下：
\begin{minted}{Rust}
    pub fn git_repository_open(out: *mut *mut git_repository,
                               path: *const c_char) -> c_int;
\end{minted}

\texttt{libgit2} public的头文件使用typedef定义了一个不完全的\texttt{git\_repository}类型：
\begin{minted}{C}
    typedef struct git_repository git_repository;
\end{minted}

因为这个类型的详情是private的，因此public的头文件中并没有定义\\
\texttt{struct git\_repository}，以确保库的使用者永远不能自己构造这个类型的实例。在Rust中一种可行的定义不完全的struct类型的方法是：
\begin{minted}{Rust}
    #[repr(C)] pub struct git_repository { _private: [u8; 0] }
\end{minted}

这个struct类型包含一个没有元素的数组。因为\texttt{\_private}字段不是\texttt{pub}的，所以不能在这个模块之外构造这个类型的值，正好对应了\texttt{libgit2}中不应该手动构造的类型。这种类型只能通过原始指针来进行操作。

手动编写一个巨大的\texttt{extern}块可能会很繁琐。如果你正在编写一个复杂的C库的Rust接口，你可能会想尝试\texttt{bindgen} crate，它包含一些可以在构建脚本中使用的函数，这些函数可以解析C头文件并自动生成相应的Rust声明。这里我们没有篇幅去介绍\texttt{bindgen}，但\href{https://crates.io/crates/bindgen}{crates.io上的\texttt{bindgen}的页面}包含了它的文档的链接。

接下来我们将完全重写\emph{main.rs}。首先，我们需要声明\texttt{raw}模块：
\begin{minted}{Rust}
    mod raw;
\end{minted}

根据\texttt{libgit2}的惯例，可能失败的函数会返回一个整数码，成功时整数码为正数或者0，失败时整数码为负数。如果有错误发生，\texttt{giterr\_last}函数将会返回一个\texttt{git\_error}结构体的指针，这个结构体会提供更多有关错误的细节。\texttt{libgit2}持有这个结构体，所以我们不需要自己free它，但它可能被后续的调用覆盖。一个恰当的Rust接口应该使用\texttt{Result}，但是在原始的版本中，我们将按照\texttt{libgit2}的方式使用它的函数，因此我们将编写我们自己的函数来处理错误：
\begin{minted}{Rust}
    use std::ffi::CStr;
    use std::os::raw::c_int;

    fn check(activity: &'static str, status: c_int) -> c_int {
        if status < 0 {
            unsafe {
                let error = &*raw::giterr_last();
                println!("error while {}: {} ({})", activity,
                         CStr::from_ptr(error.message).to_string_lossy(),
                         error.klass);
                std::process::exit(1);
            }
        }
        status
    }
\end{minted}

我们将使用这个函数检查\texttt{libgit2}调用的结果：
\begin{minted}{Rust}
    check("initializing library", raw::git_libgit2_init());
\end{minted}

这里使用了之前用过的\texttt{CStr}的方法：使用\texttt{from\_ptr}来从C字符串构造一个\texttt{CStr}，使用\texttt{to\_string\_lossy}来把它转换成Rust可以打印的类型。

接下来，我们需要一个函数来打印出一次commit：
\begin{minted}{Rust}
    unsafe fn show_commit(commit: *const raw::git_commit) {
        let author = raw::git_commit_author(commit);

        let name = CStr::from_ptr((*author).name).to_string_lossy();
        let email = CStr::from_ptr((*author).email).to_string_lossy();
        println!("{} <{}>\n", name, email);

        let message = raw::git_commit_message(commit);
        println!("{}", CStr::from_ptr(message).to_string_lossy());
    }
\end{minted}

给定一个\texttt{git\_commit}的指针，\texttt{show\_commit}会调用\texttt{git\_commit\_author}和\\
\texttt{git\_commit\_message}来获取它需要的信息。这两个函数遵循了\texttt{libgit2}在文档中解释的一个惯例：

\emph{如果一个函数返回一个对象作为返回值，那个这个函数是一个getter，并且对象的生命周期被绑定到父对象上。}

用Rust的术语来说就是，\texttt{author}和\texttt{message}是从\texttt{commit}的借用，所以\texttt{show\_commit}不需要自己释放它们，但它绝对不能在\texttt{commit}被释放之后仍然持有它们。因为这个API使用了原始指针，所以Rust不会为我们检查它们的生命周期：如果我们意外地创建了悬垂指针，可能直到程序崩溃时我们才会发现。

上面的代码假设这些字段都持有UTF-8文本，但这个假设并不总是正确的。Git也允许其他的编码。正确地解析这些字符串可能需要使用\texttt{encoding} crate。为了保持简洁，我们在这里忽略这些问题。

我们程序的\texttt{main}函数如下：
\begin{minted}{Rust}
    use std::ffi::CString;
    use std::mem;
    use std::ptr;
    use std::os::raw::c_char;

    fn main() {
        let path = std::env::args().skip(1).next()
            .expect("usage: git-toy PATH");
        let path = CString::new(path)
            .expect("path contains null characters");

        unsafe {
            check("initializing library", raw::git_libgit2_init());

            let mut repo = ptr::null_mut();
            check("opening repository",
                  raw::git_repository_open(&mut repo, path.as_ptr()));
                
            let c_name = b"HEAD\0".as_ptr() as *const c_char;
            let oid = {
                let mut oid = mem::MaybeUninit::uninit();
                check("looking up HEAD",
                      raw::git_reference_name_to_id(oid.as_mut_ptr(), repo, c_name));
                oid.assume_init()
            };

            let mut commit = ptr::null_mut();
            check("looking up commit",
                  raw::git_commit_lookup(&mut commit, repo, &oid));
            
            show_commit(commit);

            raw::git_commit_free(commit);

            raw::git_repository_free(repo);

            check("shutting down library", raw::git_libgit2_shutdown);
        }
    }
\end{minted}

这段代码首先处理path参数并初始化库，都是我们之前见过的内容。第一行有趣的代码是：
\begin{minted}{Rust}
    let mut repo = ptr::null_mut();
    check("opening repository",
          raw::git_repository_open(&mut repo, path.as_ptr()));
\end{minted}

对\texttt{git\_repository\_open}的调用会尝试在打开指定路径的Git仓库。如果它成功了，它会分配一个新的\texttt{git\_repository}对象并设置\texttt{repo}指向它。Rust隐式把引用转换成原始指针，因此这里传递的\texttt{\&mut repo}提供了调用所需的\texttt{*mut *mut git\_repository}参数。

这里展示了另一个\texttt{libgit2}在使用中的惯例（摘自\texttt{libgit2}的文档）：

\emph{以指针的指针形式的第一个参数返回的对象由调用者持有，调用者负责释放它们。}

用Rust的术语来说就是，像\texttt{git\_repository\_open}这样的函数会把新值传递给调用者。

接下来，考虑查找仓库当前head commit的对象哈希值的代码：
\begin{minted}{Rust}
    let oid = {
        let mut oid = mem::MaybeUninit::uninit();
        check("looking up HEAD",
              raw::git_reference_name_to_id(oid.as_mut_ptr(), repo, c_name));
        oid.assume_init()
    };
\end{minted}

\texttt{git\_oid}类型存储了一个对象的标识符——Git内部使用的一个160位的哈希值，Git使用它来识别commit、文件的不同版本，等等。对\texttt{git\_reference\_name\_to\_id}的调用会查找当前\texttt{"HEAD"} commit的对象标识符。

在C语言中向函数传递一个指针，然后在函数里填充指针指向对象的值是一种非常常用的初始化变量的方法，\texttt{git\_reference\_name\_to\_id}的第一个参数也是这样的。但是Rust不允许我们借用一个未初始化变量的引用。我们可以用0值初始化\texttt{oid}，但这是一种浪费：因为之后存储在这里的值都会被覆盖。

要求Rust给我们未初始化的内存是可行的，但因为在任何时刻读取未初始化的内存都是未定义行为，因此Rust提供了一个抽象\texttt{MaybeUninit}来方便使用。\texttt{MaybeUninit<T>}告诉编译器为你的类型\texttt{T}分配足够的内存空间，但并不能访问这块空间，直到你声明现在已经可以安全地访问了。当这块内存的所有权属于\texttt{MaybeUninit}时，编译器也可以避免一些可能会导致未定义行为的特殊优化，否则即使你没有在代码中显式地访问未初始化的内存，这些优化也可能导致未定义行为。

\texttt{MaybeUninit}提供了一个方法\texttt{as\_mut\_ptr()}，它产生一个指向它持有的未初始化内存的\texttt{*mut T}指针。把这个指针传给用于初始化内存的外部函数，然后调用\texttt{MaybeUninit}的unsafe\\
的\texttt{assume\_init}方法来产生一个完全初始化过的\texttt{T}。这样既不会有先初始化后立刻重新初始化的开销，也可以避免未定义行为。\texttt{assume\_init}是unsafe的，因为如果在并没有正确初始化的内存上调用它会立刻导致未定义行为。

在这个例子中是安全的，因为\texttt{git\_reference\_name\_to\_id}会初始化\texttt{MaybeUninit}拥有的内存。我们也可以将\texttt{MaybeUninit}用于\texttt{repo}和\texttt{commit}变量，但因为它们只是单个字，所以我们只是直接将它们初始化为空：
\begin{minted}{Rust}
    let mut commit = ptr::null_mut();
    check("looking up commit",
          raw::git_commit_lookup(&mut commit, repo, &oid));
\end{minted}

这里接受commit的对象标识符然后查找commit，成功时把一个\texttt{git\_commit}对象的指针存储在\texttt{commit}中。

\texttt{main}函数的其他部分应该是不言自明的。它调用了之前定义的\texttt{show\_commit}函数，然后释放了commit和repository对象，最后关闭了库。

现在我们可以在任何Git仓库上尝试我们的程序：
\begin{minted}{text}
    $ cargo run /home/jimb/rbattle
        Finished dev [unoptimized + debuginfo] target(s) in 0.0 secs
         Running `target/debug/git-toy /home/jimb/rbattle`
    Jim Blandy <jimb@red-bean.com>

    Animate goop a bit.
\end{minted}

\section{一个libgit2的安全接口}\label{SafeInter}
\texttt{libgit2}的原始接口是一个unsafe特性的完美示例：它完全可以被正确地使用（正如我们在这里所做的一样），但Rust不能强迫这些你必须遵守的规则。为一个类似这样的库设置一个safe的API的主要问题是识别所有这些规则，然后将所有违反规则的行为转换为违反借用后检查的错误。

这里是程序使用到的\texttt{libgit2}的特性的规则：
\begin{enumerate}
    \item 在调用库中任何其他函数之前，你必须先调用\texttt{git\_libgit2\_init}。在调用\\
    \texttt{git\_libgit2\_shutdown}之后你不能再调用库中任何一个函数。
    \item 除了输出参数之外，传递给\texttt{libgit2}函数的所有值必须是必须是完全初始化的。
    \item 当一次调用失败时，用来存储调用结果的输出参数仍然保持未初始化，你绝对不能使用它的值。
    \item 一个\texttt{git\_commit}对象引用了派生它的\texttt{git\_repository}对象，因此前者的生命周期绝对不能比后者长。（这一点并没有在\texttt{libgit2}的文档中说明；我们根据接口中的某些函数的存在推断出这一点，并通过阅读源码证实了它。）
    \item 类似的，一个\texttt{git\_signature}也总是借用自一个给定的\texttt{git\_commit}，因此前者的生命周期绝对不能比后者长。（文档并没有覆盖这一点。）
    \item 一个commit关联的message和author的name和email总是借用自commit，因此绝对不能在commit被free之后再使用它们。
    \item 一旦一个\texttt{libgit2}对象被释放之后，绝不能再次使用它。
\end{enumerate}

事实证明，你可以构建出一个强迫所有这些规则的\texttt{libgit2}的Rust接口，可能是通过Rust的类型系统，也可能是通过内部管理细节。

在我们开始之前，让我们重新组织一下这个项目。我们希望有一个\texttt{git}模块用来导出safe的接口，之前的程序中的原始接口作为一个private的子模块。

整个源码树看起来像这样：
\begin{minted}{text}
    git-toy/
    |-- Cargo.toml
    |-- build.rs
    |-- src/
        |-- main.rs
        |-- git/
            |-- mod.rs
            |-- raw.rs
\end{minted}

按照我们在\nameref{ModInFile}中介绍过的规则，\texttt{git}模块的源码应该在\emph{git/mod.rs}，\texttt{git::raw}子模块的源码应该在\emph{git/raw.rs}。

我们将完全重写\emph{main.rs}。它应该以\texttt{git}模块的声明开始：
\begin{minted}{Rust}
    mod git;
\end{minted}

然后，我们将需要创建\emph{git}子目录并把\emph{raw.rs}移动进去：
\begin{minted}{bash}
    $ cd /home/jimb/git-toy
    $ mkdir src/git
    $ mv src/raw.rs src/git/raw.rs
\end{minted}

\texttt{git}模块需要声明它的\texttt{raw}子模块。文件\emph{src/git/mod.rs}必须声明：
\begin{minted}{Rust}
    mod raw;
\end{minted}

因为它不是\texttt{pub}的，所以这个子模块对主程序不可见。

另外我们将需要使用一些\texttt{libc} crate里的函数，因此我们必须在\emph{Cargo.toml}中添加依赖。现在这个文件完整的内容是：
\begin{minted}{toml}
    [package]
    name = "git-toy"
    version = "0.1.0"
    authors = ["You <you@example.com>"]
    edition = "2018"

    [dependencies]
    libc = "0.2"
\end{minted}

现在我们已经重新组织了我们的模块，让我们来考虑下错误处理。就连\texttt{libgit2}的初始化函数都可能返回一个错误码，因此我们必须在准备开始之前准备好这一点。一个规范的Rust接口需要自己的\texttt{Error}类型，它需要捕获\texttt{libgit2}的错误码和来自\texttt{giterr\_last}的错误类型和消息。一个正确的错误类型必须实现通常的\texttt{Error}、\texttt{Debug}和\texttt{Display} trait。然后，它需要自己的\texttt{Result}类型来使用这个\texttt{Error}类型。这里是\emph{src/git/mod.rs}中必须的定义：
\begin{minted}{Rust}
    use std::error;
    use std::fmt;
    use std::result;

    #[derive(Debug)]
    pub struct Error {
        code: i32,
        message: String,
        class: i32
    }

    impl fmt::Display for Error {
        fn fmt(&self, f: &mut fmt::Formatter) -> result::Result<(), fmt::Error> {
            // Display 一个`Error`只需要display `libgit2`的错误信息
            self.message.fmt(f)
        }
    }

    impl error::Error for Error { }
    
    pub type Result<T> = result::Result<T, Error>;
\end{minted}

为了检查原始库调用的结果，这个模块需要一个函数用来把一个\texttt{libgit2}返回的错误码转换成一个\texttt{Result}：
\begin{minted}{Rust}
    use std::os::raw::c_int;
    use std::ffi::CStr;

    fn check(code: c_int) -> Result<c_int> {
        if code >= 0 {
            return Ok(code);
        }

        unsafe {
            let error = raw::giterr_last();

            // libgit2保证(*error).message总是非空并且以空字符结尾
            // 所以这里的调用是安全的。
            let message = CStr::from_ptr((*error).message)
                .to_string_lossy()
                .into_owned();
            
            Err(Error {
                code: code as i32,
                message,
                class: (*error).klass as i32
            })
        }
    }
\end{minted}

这个函数和之前原始版本里的\texttt{check}函数的主要区别就是它构建一个\texttt{error}而不是打印出错误信息然后立即退出。

现在我们已经准备好处理\texttt{libgit2}的初始化了。safe的接口将提供一个\texttt{Repository}类型来表示一个打开的Git仓库，它有一些方法用来解析引用、查找commit等等。继续在\emph{git/mod.rs}中实现\texttt{Repository}的定义：
\begin{minted}{Rust}
    /// 一个Git仓库
    pub struct Repository {
        // 它必须总是指向一个还在生存的`git_repository`结构体，
        // 不能有别的`Repository`也指向同一个结构体。
        raw: *mut raw::git_repository
    }
\end{minted}

\texttt{Repository}的\texttt{raw}字段并不是public的。因为只有这个模块中的代码可以访问\\
\texttt{raw::git\_repository}指针，所以保证这个模块是正确的就可以保证指针总是被正确地使用。

如果创建一个\texttt{Repository}的唯一方法就是成功打开一个新的Git仓库，那么将能确保每个\texttt{Repository}都指向一个不同的\texttt{git\_repository}对象：
\begin{minted}{Rust}
    use std::path::Path;
    use std::ptr;

    impl Repository {
        pub fn open<P: AsRef<Path>>(path: P) -> Result<Repository> {
            ensure_initialized();

            let path = path_to_cstring(path.as_ref())?;
            let mut repo j= ptr::null_mut();
            unsafe {
                check(raw::git_repository_open(&mut repo, path.as_ptr()))?;
            }
            Ok(Repository { raw: repo })
        }
    }
\end{minted}

因为safe接口里想做任何事都必须先创建一个\texttt{Repository}值，而且\texttt{Repository::open}里首先是一个\texttt{ensure\_initialized}的调用，所以我们可以确信\texttt{ensure\_initialized}会在任何\texttt{libgit2}函数之前被调用。它的定义如下：
\begin{minted}{Rust}
    fn ensure_initialized() {
        static ONCE: std::sync::Once = std::sync::Once::new();
        ONCE.call_once(|| {
            unsafe {
                check(raw::git_libgit2_init())
                    .expect("initializing libgit2 failed");
                assert_eq!(libc::atexit(shutdown), 0);
            }
        });
    }

    extern fn shutdown() {
        unsafe {
            if let Err(e) = check(raw::git_libgit2_shutdown()) {
                eprintln!("shutting down libgit2 failed: {}", e);
                std::process::abort();
            }
        }
    }
\end{minted}

\texttt{std::sync::Once}类型帮助我们以一种线程安全的方式运行初始化代码。只有第一个调用\texttt{ONCE.call\_once}的线程会运行给定的闭包。任何之后的调用，不管是这个线程还是别的线程，都会阻塞住直到第一次调用结束，然后它们会立刻返回，不会再次运行给定的闭包。一旦闭包结束之后，调用\texttt{ONCE.call\_once}的开销就非常小了，只需要原子地读取一个存储在\texttt{ONCE}里的标记。

在上面的代码中，初始化闭包调用了\texttt{git\_libgit2\_init}然后检查结果。它稍微简化了一下，直接用\texttt{expect}来保证初始化成功，而没有尝试把错误传播回调用者。

为了确保程序会调用\texttt{git\_libgit2\_shutdown}，初始化闭包使用了C库的\texttt{atexit}函数，它接受一个在退出进程之前要调用的函数的指针。Rust的闭包不能用作C函数的指针：一个闭包实际上是一个匿名类型的值，它还携带着它捕获或者引用到的值；一个C函数指针只是一个指针。然而，Rust的\texttt{fn}类型可以用作函数指针，只要你用\texttt{extern}来声明它们以让Rust知道要使用C的惯例。本地函数\texttt{shutdown}里调用\texttt{git\_libgit2\_shutdown}并保证\texttt{libgit2}正确地退出。

在\nameref{unwind}中，我们提到过跨语言边界的panic是未定义行为。从\texttt{atexit}到\texttt{shutdown}的调用就是这样一个边界，因此\texttt{shutdown}不能panic。这就是为什么\texttt{shutdown}没有简单地使用\texttt{.expect}来处理\texttt{raw::git\_libgit2\_shutdown}返回的错误，而是手动汇报错误并中断进程。POSIX禁止在\texttt{atexit}里调用\texttt{exit}，因此\texttt{shutdown}调用了\texttt{std::process::abort}来突然终止程序。

更早地调用\texttt{git\_libgit2\_shutdown}也是可行的，即在最后一个\texttt{Repository}值被drop的时候调用。但无论如何，调用\texttt{git\_libgit2\_shutdown}必须是safe API的责任。在调用它时，任何现存的\texttt{libgit2}对象都会变为不能再使用的状态，因此一个safe API绝对不能直接暴露这个函数。

一个\texttt{Repository}的原始指针必须总是指向一个还在生存的\texttt{git\_repository}对象。这隐含了关闭一个仓库的唯一方法是drop掉拥有它的\texttt{Repository}：
\begin{minted}{Rust}
    impl Drop for Repository {
        fn drop(&mut self) {
            unsafe {
                raw::git_repository_free(self.raw);
            }
        }
    }
\end{minted}

通过在指向\texttt{raw::git\_repository}的指针即将销毁时调用\texttt{git\_repository\_free}，\\
\texttt{Repository}类型还可以保证这个指针永远不会在free掉之后再被使用。

\texttt{Repository::open}方法使用了一个叫做\texttt{path\_to\_cstring}的private函数，它有两个定义——一个用于类Unix系统，一个用于Windows：
\begin{minted}{Rust}
    use sdt::ffi::CString;

    #[cfg(unix)]
    fn path_to_cstring(path: &Path) -> Result<CString> {
        // `as_bytes`方法只在类Unix系统中存在。
        use std::os::unix::ffi::OsStrExt;

        Ok(CString::new(path.as_os_str().as_bytes())?)
    }

    #[cfg(windows)]
    fn path_to_cstring(path: &Path) -> Result<CString> {
        // 尝试转换为UTF-8。如果失败了，libgit2就不能处理这个路径了。
        match path.to_str() {
            Some(s) => Ok(CString::new(s)?),
            None => {
                let message = format!("Couldn't convert path '{}' to UTF-8",
                                      path.display());
                Err(message.into())
            }
        }
    }
\end{minted}

\texttt{libgit2}的接口让这段代码变得更棘手一点。在所有的平台上，\texttt{libgit2}都以空字符结尾的C字符串接受路径。在Windows上，\texttt{libgit2}假设这些C字符串持有有效的UTF-8，并在内部把它们转换成Windows实际要求的16位路径。这通常是可以工作的，但并不是最理想的方案。Windows允许文件名不是有效的Unicode，因此也不能用UTF-8来表示。如果你有一个这样的文件，那么不可能把它的名字传递给\texttt{libgit2}。

在Rust中，文件系统路径的正确表示是\texttt{std::path::Path}，它被精心设计用来处理任何可能出现在Windows或POSIX上的路径。这意味着有一些Windows上的\texttt{Path}值不能传递给\texttt{libgit2}，因为它们不是有效的UTF-8。因此尽管\texttt{path\_to\_cstring}的行为不是很理想，但它确实是最好的方案了。

两个\texttt{path\_to\_cstring}的定义都依赖到我们的\texttt{Error}类型的转换：\texttt{?}运算符会尝试这样的转换，并且Windows版本的显式调用了\texttt{.into()}。这些转换并不起眼：
\begin{minted}{Rust}
    impl From<String> for Error {
        fn from(message: String) -> Error {
            Error { code: -1, message, class: 0 }
        }
    }

    // NulError是如果字符里有0字节时`CString::new`会返回的Error类型
    impl From<std::ffi::NulError> for Error {
        fn from(e: std::ffi::NulError) -> Error {
            Error { code: -1, message: e.to_string(), class: 0 }
        }
    }
\end{minted}

接下来，让我们看看如何把一个Git引用解析到一个对象标识符。因为一个对象标识符只是一个20字节的哈希值，因此直接在safe API中暴露它也是完全没问题的：
\begin{minted}{Rust}
    /// 一些存储在Git对象数据库中的对象（commit、tree、blob、tag等）的标识符。
    /// 它是对象内容的哈希值。
    pub struct Oid {
        pub raw: raw::git_oid
    }
\end{minted}

我们将给\texttt{Repository}添加一个方法来执行查找功能：
\begin{minted}{Rust}
    use std::mem;
    use std::os::raw::c_char;

    impl Repository {
        pub fn reference_name_to_id(&self, name: &str) -> Result<Oid> {
            let name = CString::new(name)?;
            unsafe {
                let oid = {
                    let mut oid = mem::MaybeUninit::uninit();
                    check(raw::git_reference_name_to_id(
                        oid.as_mut_ptr(), self.raw,
                        name.as_ptr() as *const c_char
                    ))?;
                    oid.assume_init()
                };
                Ok(Oid { raw: oid })
            }
        }
    }
\end{minted}

尽管在查找失败时\texttt{oid}仍然保持未初始化的状态，但这个函数通过遵循Rust的\texttt{Result}的惯例保证它的调用者永远不可能看到这个未初始化的值：调用者要么得到一个携带着正确初始化的\texttt{Oid}值的\texttt{Ok}，要么得到一个\texttt{Err}。

接下来，这个模块需要一种方法从仓库里提取commit，我们的\texttt{Commit}类型定义如下：
\begin{minted}{Rust}
    use std::marker::PhantomData;

    pub struct Commit<'repo> {
        // 这个指针总是指向一个可用的`git_commit`结构体。
        raw: *mut raw::git_commit,
        _marker: PhantomData<&'repo Repository>
    }
\end{minted}

正如我们之前提到的一样，一个\texttt{git\_commit}对象的生命周期必须不长于它引用的\\
\texttt{git\_repository}对象。Rust的生命周期可以让代码精准地捕获这个规则。

本章之前的\texttt{RefWithFlag}例子使用了一个\texttt{PhantomData}字段来告诉Rust在对待一个类型时，把它看成持有一个给定生命周期的引用，即使这个类型显然并不持有这样的引用。\texttt{Commit}类型需要类似的操作。在这个例子中，\texttt{\_marker}字段的类型是\texttt{PhantomData<\&'repo Repository>}，它告诉Rust应该把\texttt{Commit<'repo>}看成好像持有一个生命周期是\texttt{'repo}\\
的\texttt{Repository}的引用。

查找一个commit的方法如下：
\begin{minted}{Rust}
    impl Repository {
        pub fn find_commit(&self, oid: &Oid) -> Result<Commit> {
            let mut commit = ptr::null_mut();
            unsafe {
                check(raw::git_commit_lookup(&mut commit, self.raw, &oid.raw))?;
            }
            Ok(Commit { raw: commit, _marker: PhantomData })
        }
    }
\end{minted}

这是怎么把\texttt{Commit}的生命周期关联到\texttt{Repository}的生命周期的？根据\nameref{OmitLifeTime}中列出的规则，\texttt{find\_commit}的签名省略了引用的生命周期。如果我们写出生命周期的话，完整的签名应该是：
\begin{minted}{Rust}
    fn find_commit<'repo, 'id>(&'repo self, oid: &'id Oid)
        -> Result<Commit<'repo>>
\end{minted}

这正是我们想要的：Rust对待返回的\texttt{Commit}时就好像它借用了\texttt{self}（即\texttt{Repository}）一样。

当一个\texttt{Commit}被drop时，它必须释放它的\texttt{raw::git\_commit}：
\begin{minted}{Rust}
    impl<'repo> Drop for Commit<'repo> {
        fn drop(&mut self) {
            unsafe {
                raw::git_commit_free(self.raw);
            }
        }
    }
\end{minted}

你可以从一个\texttt{Commit}借用一个\texttt{Signature}（一个name和email地址）和commit message的文本：
\begin{minted}{Rust}
    impl <'repo> Commit<'repo> {
        pub fn author(&self) -> Signature {
            unsafe {
                Signature {
                    raw: raw::git_commit_author(self.raw),
                    _marker: PhantomData
                }
            }
        }

        pub fn message(&self) -> Option<&str> {
            unsafe {
                let message = raw::git_commit_message(self.raw);
                char_ptr_to_str(self, message)
            }
        }
    }
\end{minted}

这是\texttt{Signature}类型：
\begin{minted}{Rust}
    pub struct Signature<'text> {
        raw: *const raw::git_signature,
        _marker: PhantomData<&'text str>
    }
\end{minted}

一个\texttt{git\_signature}对象总是从其他地方借用内容；具体来说，\texttt{git\_commit\_author}返回的签名从\texttt{git\_commit}借用文本。因此我们的safe \texttt{Signature}类型包含一个\texttt{PhantomData<\&'text str>}来告诉Rust把它看做好像持有一个生命周期是\texttt{'text}的\texttt{\&str}。和之前一样，不需要我们写任何内容，\texttt{Commit::author}也可以正确地把它返回的\texttt{Signature}的\texttt{'text}生命周期关联到那个\texttt{Commit}的生命周期。\texttt{Commit::message}方法对持有commit message的\texttt{Option<\&str>}也实现了相同的效果。

一个\texttt{Signature}包含一些获取author的name和email地址的方法：
\begin{minted}{Rust}
    impl<'text> Signature<'text> {
        /// 以`&str`返回author的name，
        /// 如果不是有效的UTF-8则返回`None`
        pub fn name(&self) -> Option<&str> {
            unsafe {
                char_ptr_to_str(self, (*self.raw).name)
            }
        }

        /// 以`&str`返回author的email，
        /// 如果不是有效的UTF-8则返回`None`
        pub fn email(&self) -> Option<&str> {
            unsafe {
                char_ptr_to_str(self, (*self.raw).email)
            }
        }
    }
\end{minted}

上面的方法依赖于一个private的工具函数\texttt{char\_ptr\_to\_str}：
\begin{minted}{Rust}
    /// 尝试从`ptr`借用一个`&ste`，给定的`ptr`可能为空或者指向无效的UTF-8.
    /// 给返回结果赋予一个好像从`_owner`借用的生命周期。
    ///
    /// 安全性：如果`ptr`非空，它必须指向一个空字符结尾的C字符串，
    /// 这个字符串的生命周期必须至少和`_owner`一样长。
    unsafe fn char_ptr_to_str<T>(_owner: &T, ptr: *const c_char)
        -> Option<&str> {
        if ptr.is_null() {
            return None;
        } else {
            CStr::from_ptr(ptr).to_str().ok()
        }
    }
\end{minted}

\texttt{\_owner}参数的值永远不会被使用，但它的生命周期会被用到。如果显式地标注这个函数的生命周期将是：
\begin{minted}{Rust}
    fn char_ptr_to_str<'o, T: 'o>(_owner: &'o T, ptr: *const c_char)
        -> Option<&'o str>
\end{minted}

\texttt{CStr::from\_ptr}函数会返回一个生命周期不受限的\texttt{\&CStr}，因为它是从原始指针借用的。不受限的生命周期几乎总是不好的，因此应该尽可能地约束它们。包含\texttt{\_owner}参数会导致Rust把它的生命周期赋给返回值，因此调用者可以得到一个被更加精确地约束的引用。

尽管\texttt{libgit2}的文档相当不错，但它并没有说清楚\texttt{git\_signature}的\texttt{email}和\texttt{author}是否可以为空。我们在源代码套索掘了一段时间，但无法说服自己它一定不为空，最后决定让\texttt{char\_ptr\_to\_str}最好为空指针做准备，以防万一。在Rust中，这种问题可以通过类型快速回答：如果是\texttt{\&str}，那你可以确信一定有一个字符串；如果是\texttt{Option<\&str>}，那就可能为空。

最后，我们为所需的所有功能提供了safe的接口。\emph{src/main.rs}中的新\texttt{main}函数就简单很多，并且看起来更像真正的Rust代码了：
\begin{minted}{Rust}
    fn main() {
        let path = std::env::args_os().skip(1).next()
            .expect("usage: git-toy PATH");

        let repo = git::Repository::open(&path)
            .expect("opening repository");
        
        let commit_oid = repo.reference_name_to_id("HEAD")
            .expect("looking up 'HEAD' reference");
        
        let commit = repo.find_commit(&commit_oid)
            .expect("looking up commit");
        
        let author = commit.author();
        println!("{} <{}>\n",
                 author.name().unwrap_or("(none)"),
                 author.email().unwrap_or("none"));
        
        println!("{}", commit.message().unwrap_or("(none)"));
    }
\end{minted}

在本章中，我们从最简单的不提供很多安全性保证的接口开始，到通过以内部的unsafe API为基础，把任何对合约的违反都转换成Rust的类型错误来构建出safe的API。最后得到的结果是一个Rust可以保证被正确使用的接口。在大多数情况下，我们让Rust强迫的规则都是C和C++程序自己隐式遵守的那些规则。Rust之所以感觉起来比C和C++严格得多，并不是因为这些规则很陌生，而是因为这种规则被机械地、全面地强制。

\section{总结}
Rust并不是一门简单的语言。它的目标横跨了两个不同的世界。它是一门现代化的编程语言，以安全为设计原则，同时还有像闭包和迭代器这样的便利设施，但它的目标是让你能以最小的运行时开销来控制机器的原始能力。

这门语言的轮廓由这些目标决定。Rust设法用safe代码来实现大部分的功能。它的借用检查器和0开销抽象让你尽可能地接近裸机，同时还没有未定义行为的风险。当这些还不够或者当你想使用现有的C代码时，unsafe代码和外部函数接口已准备就绪。但再重复一次，它并不是只提供给你unsafe特性然后祝你好运，它的目标总是使用unsafe特性来构建safe的API。这正是我们基于\texttt{libgit2}所做的工作。这也是Rust的团队对\texttt{Box}、\texttt{Vec}、其他集合、channel等等设施所做的工作：标注库充满了用unsafe代码实现的safe的抽象。

Rust的雄心也许并不是成为最简单的工具。但Rust是安全、快速、并发、高效的。使用它来构建大型、快速、安全、强大的系统，充分利用它们所运行的硬件的力量。使用它来让软件变得更好。
